#!/bin/bash

# Script to convert Windows line endings (CRLF) to Linux line endings (LF)
# for all files in subdirectories

set -e

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Function to display usage
usage() {
    echo "Usage: $0 [OPTIONS]"
    echo "Convert Windows line endings (CRLF) to Linux line endings (LF) in all subdirectory files"
    echo ""
    echo "Options:"
    echo "  -d, --dry-run     Show what would be changed without making changes"
    echo "  -e, --extensions  Comma-separated list of file extensions to process (default: all text files)"
    echo "  -h, --help        Show this help message"
    echo ""
    echo "Note: Files matching patterns in .gitignore will be automatically excluded from processing."
    echo ""
    echo "Examples:"
    echo "  $0                          # Convert all text files (respecting .gitignore)"
    echo "  $0 --dry-run               # Preview changes without applying"
    echo "  $0 -e 'txt,js,py,java'     # Only process specific extensions"
}

# Default options
DRY_RUN=false
EXTENSIONS=""

# Parse command line arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        -d|--dry-run)
            DRY_RUN=true
            shift
            ;;
        -e|--extensions)
            EXTENSIONS="$2"
            shift 2
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            echo -e "${RED}Unknown option: $1${NC}"
            usage
            exit 1
            ;;
    esac
done

# Cache for gitignore patterns - load once for efficiency
declare -a GITIGNORE_PATTERNS
declare -a GITIGNORE_NEGATIONS
GITIGNORE_LOADED=false

# Function to load gitignore patterns into cache
load_gitignore_patterns() {
    local gitignore_file=".gitignore"
    
    # If no .gitignore file exists, mark as loaded with empty patterns
    if [[ ! -f "$gitignore_file" ]]; then
        GITIGNORE_LOADED=true
        return
    fi
    
    # Clear existing patterns
    GITIGNORE_PATTERNS=()
    GITIGNORE_NEGATIONS=()
    
    # Read and parse gitignore patterns
    while IFS= read -r pattern; do
        # Skip empty lines and comments
        [[ -z "$pattern" || "$pattern" =~ ^[[:space:]]*# ]] && continue
        
        # Remove leading/trailing whitespace
        pattern=$(echo "$pattern" | xargs)
        [[ -z "$pattern" ]] && continue
        
        # Handle negation patterns (!)
        if [[ "$pattern" =~ ^! ]]; then
            pattern="${pattern#!}"
            GITIGNORE_NEGATIONS+=("$pattern")
        else
            GITIGNORE_PATTERNS+=("$pattern")
        fi
    done < "$gitignore_file"
    
    GITIGNORE_LOADED=true
}

# Function to check if a file matches gitignore patterns
is_gitignored() {
    local file_path="$1"
    
    # Load patterns if not already loaded
    if [[ "$GITIGNORE_LOADED" != "true" ]]; then
        load_gitignore_patterns
    fi
    
    # If no patterns, nothing is ignored
    if [[ ${#GITIGNORE_PATTERNS[@]} -eq 0 ]]; then
        return 1
    fi
    
    # Remove leading ./ from path for consistent matching
    file_path="${file_path#./}"
    
    # Check negation patterns first - if file matches a negation, it should NOT be ignored
    for pattern in "${GITIGNORE_NEGATIONS[@]}"; do
        if [[ "$file_path" == $pattern ]] || [[ "$file_path" =~ ^$pattern/ ]]; then
            return 1  # File explicitly included, not ignored
        fi
    done
    
    # Check ignore patterns
    for pattern in "${GITIGNORE_PATTERNS[@]}"; do
        # Handle directory patterns (ending with /)
        if [[ "$pattern" =~ /$ ]]; then
            pattern="${pattern%/}"
            if [[ "$file_path" =~ ^$pattern/ ]]; then
                return 0  # File is in ignored directory
            fi
        else
            # Simple pattern matching - check common cases efficiently
            case "$file_path" in
                $pattern|$pattern/*|*/$pattern|*/$pattern/*)
                    return 0  # File matches pattern
                    ;;
            esac
        fi
    done
    
    return 1  # File not ignored
}

# Function to check if file has Windows line endings
has_crlf() {
    local file="$1"
    if file "$file" | grep -q "CRLF"; then
        return 0
    fi
    # Also check using od command as backup
    if od -c "$file" | grep -q '\\r\\n'; then
        return 0
    fi
    return 1
}

# Function to convert line endings
convert_file() {
    local file="$1"
    
    if [[ "$DRY_RUN" == "true" ]]; then
        echo -e "${YELLOW}[DRY RUN] Would convert: $file${NC}"
        return
    fi
    
    # Create backup
    cp "$file" "$file.bak"
    
    # Convert CRLF to LF using sed
    sed -i 's/\r$//' "$file"
    
    # Verify conversion worked
    if ! has_crlf "$file"; then
        echo -e "${GREEN}✓ Converted: $file${NC}"
        rm "$file.bak"  # Remove backup if successful
    else
        echo -e "${RED}✗ Failed to convert: $file${NC}"
        mv "$file.bak" "$file"  # Restore backup
        return 1
    fi
}

# Function to build find command based on extensions
build_find_command() {
    local base_cmd="find . -type f"
    
    if [[ -n "$EXTENSIONS" ]]; then
        # Split extensions and build -name patterns
        IFS=',' read -ra EXT_ARRAY <<< "$EXTENSIONS"
        local name_patterns=""
        for ext in "${EXT_ARRAY[@]}"; do
            ext=$(echo "$ext" | xargs)  # Trim whitespace
            if [[ -n "$name_patterns" ]]; then
                name_patterns="$name_patterns -o"
            fi
            name_patterns="$name_patterns -name '*.${ext}'"
        done
        base_cmd="$base_cmd \( $name_patterns \)"
    else
        # Exclude common binary file patterns
        base_cmd="$base_cmd ! -path '*/.*' ! -name '*.exe' ! -name '*.bin' ! -name '*.so' ! -name '*.dll' ! -name '*.jar' ! -name '*.class' ! -name '*.pyc' ! -name '*.o' ! -name '*.a' ! -name '*.png' ! -name '*.jpg' ! -name '*.jpeg' ! -name '*.gif' ! -name '*.pdf' ! -name '*.zip' ! -name '*.tar' ! -name '*.gz' ! -name '*.bz2'"
    fi
    
    echo "$base_cmd"
}

# Main execution
echo -e "${GREEN}Starting line ending conversion...${NC}"

if [[ "$DRY_RUN" == "true" ]]; then
    echo -e "${YELLOW}DRY RUN MODE - No files will be modified${NC}"
fi

# Check if required tools are available
if ! command -v file &> /dev/null; then
    echo -e "${RED}Error: 'file' command not found. Please install file utilities.${NC}"
    exit 1
fi

if ! command -v sed &> /dev/null; then
    echo -e "${RED}Error: 'sed' command not found.${NC}"
    exit 1
fi

# Build and execute find command
find_cmd=$(build_find_command)
files_processed=0
files_converted=0
files_with_crlf=0

echo "Scanning files..."

# Use process substitution to handle filenames with spaces
while IFS= read -r -d '' file; do
    # Skip if file is not readable
    if [[ ! -r "$file" ]]; then
        continue
    fi
    
    # Skip if file is actually a directory (shouldn't happen with -type f, but just in case)
    if [[ -d "$file" ]]; then
        continue
    fi
    
    # Skip if file matches gitignore patterns
    if is_gitignored "$file"; then
        continue
    fi
    
    files_processed=$((files_processed + 1))
    
    # Check if file has CRLF line endings
    if has_crlf "$file"; then
        files_with_crlf=$((files_with_crlf + 1))
        if convert_file "$file"; then
            files_converted=$((files_converted + 1))
        fi
    fi
    
    # Show progress every 100 files
    if (( files_processed % 100 == 0 )); then
        echo "Processed $files_processed files..."
    fi
    
done < <(eval "$find_cmd -print0" 2>/dev/null)

# Summary
echo ""
echo -e "${GREEN}=== Summary ===${NC}"
echo "Files processed: $files_processed"
echo "Files with CRLF: $files_with_crlf"

if [[ "$DRY_RUN" == "true" ]]; then
    echo -e "${YELLOW}Files that would be converted: $files_with_crlf${NC}"
    echo -e "${YELLOW}Run without --dry-run to apply changes${NC}"
else
    echo "Files successfully converted: $files_converted"
    if (( files_with_crlf != files_converted )); then
        echo -e "${RED}Files that failed conversion: $((files_with_crlf - files_converted))${NC}"
    fi
fi

echo -e "${GREEN}Done!${NC}"